package day02_Thread.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现Runnable完成多线程的创建步骤:
 *  1、实现Runnable接口
 *  2、重写接口中的run(),该方法是多线程运行的方法
 *  3、创建实现类对象,作为Thread的参数进行传递,通过Thread的对象调用start()方法启动多线程
 */
public class TicketRunnableLock implements Runnable//1、实现Runnable接口
{
    //定义一个变量用于存储剩余的票数,共享资源
    private int ticketNum = 5;

    private Lock lock = new ReentrantLock();

    /**
     * 三个人买票的行为/情况
     * 2、重写接口中的run(),该方法是多线程运行的方法
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            buyTicket();
        }
    }

    /**
     * 买票的方法
     */
    public void buyTicket() {

        //加锁
        lock.lock();
        //判断票数是否大于0,只有大于0时才可以购买
        if (ticketNum > 0) {
            try {
                //系统延迟
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "在买票,买了" + (ticketNum--));
        }
        //释放锁
        lock.unlock();
    }

    public static void main(String[] args) {
        //3、创建实现类对象,作为Thread的参数进行传递,通过Thread的对象调用start()方法启动多线程
        TicketRunnableLock ticketRunnable = new TicketRunnableLock();
        //创建3个线程,即三个人去买票
        Thread tom = new Thread(ticketRunnable, "tom");
        Thread jim = new Thread(ticketRunnable, "jim");
        Thread lilei = new Thread(ticketRunnable, "lilei");
        tom.start();
        jim.start();
        lilei.start();
    }

}



/*
* 在多线程编程中，当多个线程访问共享资源时，可能会出现数据不一致或竞态条件（race condition）的问题。为了解决这些问题，需要一种机制来确保在同一时间只有一个线程可以访问共享资源。这就是锁（Lock）的作用。
什么是Lock？
Lock 是 Java 提供的一种同步机制，它提供了与 synchronized 关键字类似的功能，但提供了更多的扩展性。Lock 是 java.util.concurrent.locks.Lock 接口的实例，它允许更灵活的锁定操作，包括尝试非阻塞地获取锁、可中断地获取锁以及尝试超时获取锁等。
为什么需要上锁和解锁？
在多线程环境中，当多个线程访问同一个共享资源时，如果不加以控制，可能会导致数据不一致或程序错误。上锁（Lock）的目的是确保在任一时刻只有一个线程可以访问共享资源，从而避免数据竞争。解锁（Unlock）则是在线程完成对共享资源的访问后，释放锁，使得其他等待的线程可以获得锁并访问共享资源。
如何使用Lock？
在你的代码中，使用了 ReentrantLock 类，它是 Lock 接口的一个实现。以下是如何使用 ReentrantLock 的步骤：
创建锁对象：
private Lock lock = new ReentrantLock();
在访问共享资源之前获取锁：
lock.lock();
访问共享资源：
在获取锁之后，线程可以安全地访问共享资源。
释放锁：
完成对共享资源的访问后，必须释放锁，以便其他线程可以获取锁。
lock.unlock();
示例代码分析
在你的代码中，TicketRunnableLock 类实现了 Runnable 接口，并重写了 run 方法。在 run 方法中，通过调用 buyTicket 方法模拟了购票行为。
public void buyTicket() {
    // 加锁
    lock.lock();
    try {
        // 判断票数是否大于0，只有大于0时才可以购买
        if (ticketNum > 0) {
            try {
                // 系统延迟
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "在买票,买了" + (ticketNum--));
        }
    } finally {
        // 释放锁
        lock.unlock();
    }
}
代码解释：
加锁：在访问共享资源（即剩余票数 ticketNum）之前，使用 lock.lock() 获取锁。
访问共享资源：在获取锁之后，线程可以安全地访问和修改 ticketNum。
释放锁：在 finally 块中使用 lock.unlock() 释放锁，确保无论是否发生异常，锁都会被释放，防止死锁的发生。
总结
使用 Lock 可以更灵活地控制多线程对共享资源的访问，避免了数据不一致的问题。在你的代码中，通过使用 ReentrantLock 来确保在任一时刻只有一个线程可以修改剩余票数，从而实现了线程安全的购票系统。
* */
